﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.Utilities;
using System.Xml;
using System.IO;
namespace SPHelper
{


    public interface CamlElement
    {
        XmlElement GenerateElement(XmlDocument doc);
    }

    /// <summary>
    /// Defines A Predicate that will be used in a  CAML Query statement
    /// </summary>
    public class Predicate : CamlElement
    {
        /// <summary>
        /// Defines Differnet leagel Comparison Operators
        /// </summary>
        public enum LogicalJoins
        {
            And,
            Or
        }
        private List<Predicate> predicates;
        private List<Operation> operations;
        public LogicalJoins join { get; set; }
        /// <summary>
        /// CAML Predicate clause with operator
        /// </summary>
        /// <param name="predicateStatement"></param>
        /// <param name="operatorForNextPredicate">Operator for this predicate and the following one. 
        /// If last predicate, then this operator is ignored!</param>
        public Predicate(LogicalJoins logicalJoin)
        {
            predicates = new List<Predicate>();
            operations = new List<Operation>();
            join = logicalJoin;
        }

        public void AddPredicate(Predicate predicate)
        {
            predicates.Add(predicate);
        }

        public void AddOperation(Operation operation)
        {
            operations.Add(operation);
        }

        public XmlElement GenerateElement(XmlDocument doc)
        {
            string sPredicate = null;
            switch (join)
            {
                case LogicalJoins.And:
                    sPredicate = "And";
                    break;
                case LogicalJoins.Or:
                    sPredicate = "Or";
                    break;
            }
            XmlElement ePredicate = doc.CreateElement(sPredicate);
            foreach (Operation operation in operations)
            {
                ePredicate.AppendChild(operation.GenerateElement(doc));
            }
            foreach (Predicate predicate in predicates)
            {
                ePredicate.AppendChild(predicate.GenerateElement(doc));
            }
            return ePredicate;
        }

    }

    /// <summary>
    /// Defines A field that will be used in a  CAML Query statement
    /// </summary>
    public class FieldRef : CamlElement
    {
        public bool Ascending { get; set; }
        public bool Explicit { get; set; }
        public string Name { get; set; }
        public string List { get; set; }
        public string Key { get; set; }
        public string ID { get; set; }
        public string Format { get; set; }
        public string DisplayName { get; set; }
        public string CreateURL { get; set; }
        public string Alias { get; set; }
        public string RefType { get; set; }
        public string ShowField { get; set; }
        public string TextOnly { get; set; }
        public string Type { get; set; }
        public bool LookupId { get; set; }



        public FieldRef(string fieldname, bool guid)
        {
            Ascending = true;
            Explicit = false;
            LookupId = false;
            if (!guid)
            {
                Name = fieldname;
            }
            else { ID = fieldname; }
            List = "";
            Key = "";
            ID = "";
            Format = "";
            DisplayName = "";
            CreateURL = "";
            Alias = "";
            RefType = "";
            ShowField = "";
            TextOnly = "";
            Type = "";
        }

        public FieldRef(string fieldname, string list, bool guid)
        {
            Ascending = true;
            Explicit = false;
            LookupId = false;
            if (!guid)
            {
                Name = fieldname;
            }
            else { ID = fieldname; }
            List = list;
            Key = "";
            Format = "";
            DisplayName = "";
            CreateURL = "";
            Alias = "";
            RefType = "";
            ShowField = "";
            TextOnly = "";
            Type = "";
        }



        public XmlElement GenerateElement(XmlDocument doc)
        {
            XmlElement eFieldRef = doc.CreateElement("FieldRef");
            if (Set(Name))
            {
                eFieldRef.SetAttribute("Name", Name);
            }
            if (Set(List))
            {
                eFieldRef.SetAttribute("List", List);
            }
            if (Set(Key))
            {
                eFieldRef.SetAttribute("Key", Key);
            }
            if (Set(ID))
            {
                eFieldRef.SetAttribute("ID", ID);
            }
            if (Set(Format))
            {
                eFieldRef.SetAttribute("Format", Format);
            }
            if (Set(DisplayName))
            {
                eFieldRef.SetAttribute("DisplayName", DisplayName);
            }
            if (Set(CreateURL))
            {
                eFieldRef.SetAttribute("CreateURL", CreateURL);
            }
            if (Set(Alias))
            {
                eFieldRef.SetAttribute("Alias", Alias);
            }
            if (Set(RefType))
            {
                eFieldRef.SetAttribute("RefType", RefType);
            }
            if (Set(ShowField))
            {
                eFieldRef.SetAttribute("ShowField", ShowField);
            }
            if (Set(TextOnly))
            {
                eFieldRef.SetAttribute("TextOnly", TextOnly);
            }
            if (Set(Type))
            {
                eFieldRef.SetAttribute("Type", Type);
            }
            if (Ascending)
            {
                eFieldRef.SetAttribute("Ascending", "TRUE");
            }
            if (Explicit)
            {
                eFieldRef.SetAttribute("Explicit", "TRUE");
            }
            if (LookupId)
            {
                eFieldRef.SetAttribute("LookupId", "TRUE");
            }
            return eFieldRef;
        }

        public bool Set(string var)
        {
            if (var == "")
            {
                return false;
            }
            else return true;
        }

    }

    /// <summary>
    /// Defines A Operation that will be used in a CAML Query statement
    /// </summary>
    public class Operation : CamlElement
    {
        /// <summary>
        /// Defines Differnet leagel logical joins
        /// </summary>
        public enum ComparisonOperators
        {
            IsNull,
            IsNotNull,
            EqualTo,
            GreaterThan,
            LessThan,
            GreaterThanOrEqualTo,
            LessThanOrEqualTo,
            NotEqualTo,
            NotIncludes,
            Includes,
            Contains,
            BeginsWith,
            DateRangesOverlap,
            In
        }

        public List<FieldRef> FieldRefs { get; set; }
        public ComparisonOperators Operator { get; set; }
        public List<Value> Values { get; set; }
        public string XMLData { get; set; }

        public Operation(ComparisonOperators Operator)
        {
            FieldRefs = new List<FieldRef>();
            Values = new List<Value>();
            this.Operator = Operator;
        }

        public void AddFieldRef(FieldRef fieldRef)
        {
            FieldRefs.Add(fieldRef);
        }

        public void AddValue(Value value)
        {
            Values.Add(value);
        }

        public XmlElement GenerateElement(XmlDocument doc)
        {
            string sOperation = null;

            switch (Operator)
            {
                case ComparisonOperators.BeginsWith:
                    sOperation = "BeginsWith";
                    break;
                case ComparisonOperators.Contains:
                    sOperation = "Contains";
                    break;
                case ComparisonOperators.DateRangesOverlap:
                    sOperation = "DateRangesOverlap";
                    break;
                case ComparisonOperators.EqualTo:
                    sOperation = "Eq";
                    break;
                case ComparisonOperators.GreaterThan:
                    sOperation = "Gt";
                    break;
                case ComparisonOperators.GreaterThanOrEqualTo:
                    sOperation = "Geq";
                    break;
                case ComparisonOperators.In:
                    sOperation = "In";
                    break;
                case ComparisonOperators.Includes:
                    sOperation = "Includes";
                    break;
                case ComparisonOperators.IsNotNull:
                    sOperation = "IsNotNull";
                    break;
                case ComparisonOperators.IsNull:
                    sOperation = "IsNull";
                    break;
                case ComparisonOperators.LessThan:
                    sOperation = "Lt";
                    break;
                case ComparisonOperators.LessThanOrEqualTo:
                    sOperation = "Leq";
                    break;
                case ComparisonOperators.NotEqualTo:
                    sOperation = "Neq";
                    break;
                case ComparisonOperators.NotIncludes:
                    sOperation = "NotIncludes";
                    break;
            }
            XmlElement eOperation = doc.CreateElement(sOperation);
            foreach (FieldRef field in FieldRefs)
            {
                eOperation.AppendChild(field.GenerateElement(doc));
            }
            if (Values.Count > 1)
            {
                XmlElement eValues = doc.CreateElement("Values");
                foreach (Value val in Values)
                {
                    eValues.AppendChild(val.GenerateElement(doc));
                }
                eOperation.AppendChild(eValues);
            }
            else if (Values.Count > 0)
            {
                eOperation.AppendChild(Values.ElementAt(0).GenerateElement(doc));
            }
            return eOperation;
        }
    }

    /// <summary>
    /// Defines A Value that will be used in a  CAML Query statement
    /// </summary>
    public class Value : CamlElement
    {
        /// <summary>
        /// Defines Differnet leagel Data Types
        /// </summary>
        public enum DataTypes
        {
            Integer,
            Number,
            Counter,
            Date,
            DateTime,
            User,
            Lookup,
            Text,
            Custom
        }

        public string CustomDataType { get; set; }
        public DataTypes DataType { get; set; }
        public string Val { get; set; }
        public bool IncludeTimeValue { get; set; }

        public Value(DataTypes dataType, string value)
        {
            this.DataType = dataType;
            this.Val = value;
            IncludeTimeValue = false;
        }

        public XmlElement GenerateElement(XmlDocument doc)
        {

            string sDataType = null;
            switch (DataType)
            {
                case DataTypes.Counter:
                    sDataType = "Counter";
                    break;
                case DataTypes.Date:
                    sDataType = "Date";
                    break;
                case DataTypes.DateTime:
                    sDataType = "DateTime";
                    break;
                case DataTypes.Integer:
                    sDataType = "Integer";
                    break;
                case DataTypes.Lookup:
                    sDataType = "Lookup";
                    break;
                case DataTypes.Number:
                    sDataType = "Number";
                    break;
                case DataTypes.User:
                    sDataType = "User";
                    break;
                case DataTypes.Text:
                    sDataType = "Text";
                    break;
                case DataTypes.Custom:
                    sDataType = CustomDataType;
                    break;
            }
            XmlElement eVal = doc.CreateElement("Value");
            eVal.SetAttribute("Type", sDataType);
            if (IncludeTimeValue)
            {
                eVal.SetAttribute("IncludeTimeValue", IncludeTimeValue.ToString());
            }
            eVal.InnerText = Val;
            return eVal;
        }
    }

    /// <summary>
    /// Defines A OrderBy Element that will be used in a  CAML Query statement
    /// </summary>
    /// <summary>
    /// Defines A OrderBy Element that will be used in a  CAML Query statement
    /// </summary>
    public class OrderBy : CamlElement
    {
        public bool Override { get; set; }
        public bool UseIndexForOrderBy { get; set; }
        private List<FieldRef> FieldRefs;

        public OrderBy()
        {
            FieldRefs = new List<FieldRef>();
            Override = false;
            UseIndexForOrderBy = false;
        }

        public void AddFieldRef(FieldRef fieldRef)
        {
            FieldRefs.Add(fieldRef);
        }

        public XmlElement GenerateElement(XmlDocument doc)
        {
            XmlElement eOrderBy = doc.CreateElement("OrderBy");
            eOrderBy.SetAttribute("Override", Override.ToString());
            eOrderBy.SetAttribute("UseIndexForOrderBy", UseIndexForOrderBy.ToString());
            foreach (FieldRef field in FieldRefs)
            {
                eOrderBy.AppendChild(field.GenerateElement(doc));
            }
            return eOrderBy;
        }
    }

    /// <summary>
    /// Defines A GroupBy Element that will be used in a  CAML Query statement
    /// </summary>
    public class GroupBy : CamlElement
    {
        public bool Collapse { get; set; }
        private List<FieldRef> FieldRefs;

        public GroupBy()
        {
            FieldRefs = new List<FieldRef>();
            Collapse = false;
        }

        public void AddFieldRef(FieldRef fieldRef)
        {
            FieldRefs.Add(fieldRef);
        }

        public XmlElement GenerateElement(XmlDocument doc)
        {
            XmlElement eOrderBy = doc.CreateElement("GroupBy");
            eOrderBy.SetAttribute("Collapse", Collapse.ToString());
            foreach (FieldRef field in FieldRefs)
            {
                eOrderBy.AppendChild(field.GenerateElement(doc));
            }
            return eOrderBy;
        }
    }

    /// <summary>
    /// Defines A Where Element that will be used in a  CAML Query statement
    /// </summary>
    public class Where : CamlElement
    {

        private List<Predicate> predicates;
        private List<Operation> operations;

        public Where()
        {
            predicates = new List<Predicate>();
            operations = new List<Operation>();

        }

        public void AddPredicate(Predicate predicate)
        {
            predicates.Add(predicate);
        }

        public void AddOperation(Operation operation)
        {
            operations.Add(operation);
        }

        public XmlElement GenerateElement(XmlDocument doc)
        {
            XmlElement eQuery = doc.CreateElement("Where");
            foreach (Operation operation in operations)
            {
                eQuery.AppendChild(operation.GenerateElement(doc));
            }
            foreach (Predicate predicate in predicates)
            {
                eQuery.AppendChild(predicate.GenerateElement(doc));
            }

            return eQuery;
        }
    }

    /// <summary>
    /// Defines A Query Element that will be used in a  CAML Query statement
    /// </summary>
    public class Query : CamlElement
    {
        public OrderBy orderBy { get; set; }
        public GroupBy groupBy { get; set; }
        public Where where { get; set; }

        public Query()
        {
            where = new Where();
            orderBy = new OrderBy();
            groupBy = new GroupBy();
        }

        public XmlElement GenerateElement(XmlDocument doc)
        {
            XmlElement eQuery = doc.CreateElement("Query");
            eQuery.AppendChild(where.GenerateElement(doc));
            eQuery.AppendChild(orderBy.GenerateElement(doc));
            eQuery.AppendChild(groupBy.GenerateElement(doc));
            return eQuery;
        }
    }
    #region OLD
    public class Caml
    {
        /// <summary>
        /// pass order by fields, convert into CAML query text, includes OrderBy tags if needed
        /// </summary>
        /// <param name="orderByFields"></param>
        /// <returns></returns>
        public static string FormatOrderBy(List<OrderByField> orderByFields, bool isAddOrderByTags)
        {
            string o = null;
            if (orderByFields != null && orderByFields.Count > 0)
            {
                foreach (Caml.OrderByField f in orderByFields)
                {
                    string ascendingValue = f.IsDescending ? "FALSE" : "TRUE";
                    if (!string.IsNullOrEmpty(f.FieldName))
                    {
                        o += "<FieldRef Name='" + f.FieldName + "' Ascending='" + ascendingValue + "' />";
                    }
                }
                if (isAddOrderByTags && !string.IsNullOrEmpty(o))
                {
                    o = "<OrderBy>" + o + "</OrderBy>";
                }
            }
            return o;
        }

        public class OrderByField
        {
            public string FieldName { get; set; }
            public bool IsDescending { get; set; }
            public OrderByField() { }
            public OrderByField(string fieldname, bool isDescending)
            {
                FieldName = fieldname;
                IsDescending = isDescending;
            }
            public OrderByField(string fieldname)
            {
                FieldName = fieldname;
                IsDescending = false;
            }
        }

        /// <summary>
        /// pass order by fields, convert into CAML query text, includes OrderBy tags
        /// </summary>
        /// <param name="orderByFields"></param>
        /// <returns></returns>
        public static string FormatOrderBy(List<OrderByField> orderByFields)
        {
            return FormatOrderBy(orderByFields, true);
        }

        /// <summary>
        /// Get the orderby fields from a CAML query passed
        /// </summary>
        /// <param name="query"></param>
        /// <returns></returns>
        public static string[] GetOrderByFieldsFromQuery(string query)
        {
            string[] fields = null;
            try
            {
                // init
                List<string> _fields = new List<string>();


                // queryText += "<OrderBy><FieldRef Name='PublicationDate' Ascending='FALSE' /></OrderBy>";
                if (!string.IsNullOrEmpty(query) && query.ToLower().Contains("orderby"))
                {
                    // provide root node if needed
                    if (!query.ToLower().Contains("<query")) { query = "<Query>" + query + "</Query>"; }


                    // 1.) get XML object
                    System.Xml.XmlDataDocument xml = new System.Xml.XmlDataDocument();
                    xml.LoadXml(query);

                    // 2.) get only the orderby node
                    System.Xml.XmlNode orderBy = xml.SelectSingleNode("Query/OrderBy");
                    if (orderBy == null) { orderBy = xml.SelectSingleNode("OrderBy"); }
                    if (orderBy == null) { throw new Exception("Failed to get orderby node in CAML query"); }

                    // 3.) enum child nodes and get fields
                    System.Xml.XmlNodeList fieldRefs = orderBy.ChildNodes;
                    if (fieldRefs == null || fieldRefs.Count == 0)
                    { throw new Exception("OrderBy node has no child elements"); }
                    foreach (System.Xml.XmlNode f in fieldRefs)
                    {
                        if (f.Attributes["Name"] != null && !string.IsNullOrEmpty(f.Attributes["Name"].Value))
                        {
                            _fields.Add(f.Attributes["Name"].Value);
                        }
                        else
                        {
                            SPHelper.Log.WriteError("efadtrsrcxfzvest", "Unknown child node in orderby node: " + f.InnerXml);
                        }
                    }

                    // convert list to array
                    if (_fields != null && _fields.Count > 0) { fields = _fields.ToArray(); }
                }
            }
            catch (Exception ex)
            {
                SPHelper.Log.WriteError("ghkfygudrese4sr57e68tr6t", ex, "query: " + query);
                fields = null;
            }
            return fields;
        }

        /// <summary>
        /// Convert date time into CAML readable date/time value
        /// </summary>
        /// <param name="dateTime"></param>
        /// <returns></returns>
        public static string FormatDateTime(DateTime dateTime)
        {
            return SPUtility.CreateISO8601DateTimeFromSystemDateTime(dateTime);
        }

        /// <summary>
        /// format a view field for an SPQuery, 
        /// for query.ViewFields = FormatSPQueryViewFields(string[]);
        /// These are the fields to return by SPQuery
        /// </summary>
        /// <param name="fields"></param>
        /// <returns></returns>
        public static string FormatSPQueryViewFields(string[] fields)
        {
            string o = null;
            if (fields != null && fields.Length > 0)
            {
                foreach (string field in fields)
                {
                    if (!string.IsNullOrEmpty(field) && field.Trim() != string.Empty)
                    { o += "<FieldRef Name='" + field + "' />"; }
                }
            }
            return o;
        }

        ///// <summary>
        ///// Takes collection of predicates and converts them into nest CAML predicates with AND/OR operators added
        ///// </summary>
        ///// <param name="predicates"></param>
        ///// <param name="isAddWhereTags"></param>
        ///// <returns></returns>
        //public static string FormatPredicates(List<Predicate> predicates, bool isAddWhereTags)
        //{
        //    string o = null;
        //    if (predicates != null && predicates.Count > 0)
        //    {
        //        // delete empty predicates
        //        for (int i = 0; i < predicates.Count; i++)
        //        {
        //            if (string.IsNullOrEmpty(predicates[i].PredicateStatement)
        //                || predicates[i].PredicateStatement.Trim() == string.Empty
        //                )
        //            {
        //                predicates.RemoveAt(i);
        //                i--;
        //            }
        //        }

        //        // list of open operators that still need to be closed
        //        var openOperators = new List<LogicalJoins>();

        //        // merge predicates
        //        foreach (Predicate p in predicates)
        //        {
        //            // opening operator
        //            // only add if: 1.) not the only predicate, 2.) not the last predicate
        //            if (predicates.Count > 1 && predicates[predicates.Count - 1] != p)
        //            {
        //                // adding a predicate where there are more predicates, and not the last predicate
        //                o += string.Format("<{0}>", p.Operator.ToString());
        //                openOperators.Add(p.Operator);
        //            }

        //            // predicate statement
        //            o += p.PredicateStatement;
        //        }

        //        // close operators (in reverse order)
        //        if (openOperators != null && openOperators.Count > 0)
        //        {
        //            for (int i = openOperators.Count - 1; i >= 0; i--)
        //            {
        //                o += string.Format("</{0}>", openOperators[i].ToString());
        //            }
        //        }

        //        if (isAddWhereTags && !string.IsNullOrEmpty(o)) { o = "<Where>" + o + "</Where>"; }
        //    }
        //    return o;
        //}

        /// <summary>
        /// Takes collection of predicates and converts them into nest CAML predicates with AND/OR operators added
        /// Adds Where tags
        /// </summary>
        /// <param name="predicates"></param>
        /// <returns></returns>

    }

    #endregion
}
